package memorphic.slide
{
	
	import mx.binding.utils.ChangeWatcher;
	import mx.utils.DescribeTypeCache;
	
	
	/**
	 * Implements the [Trigger] metadata.
	 * 
	 * <code>[Trigger]</code> may be used to annotate any singe-argument method or writable property, provided that it is publicly
	 * accessible, that is <code>public</code> or in a namespace (i.e. not <code>protected</code>, <code>private</code> or <code>internal</code>.)
	 * 
	 * You can use the namespace <code>slide_private</code> to indicate the intent that a property be private, while still allowing
	 * <code>[Trigger]</code> to work. 
	 */
	public class TriggerMetadataImpl
	{
		
		public var host:Object;
		
		
		
		/**
		 * @param host Object. The host to which trigger capabilities should be added
		 * @param useMetadata Boolean. If true (default) then the host's type will be automatically introspected to find [Trigger] metadata
		 */
		public function TriggerMetadataImpl(host:Object, useMetadata:Boolean=true)
		{
			this.host = host;
			if(useMetadata){
				setupTriggers();
			}
		}
		
		protected function setupTriggers():void
		{
			var type:XML = DescribeTypeCache.describeType(host).typeDescription;
			var triggers:Array = new TriggerInfo(type).getTriggers();
			var triggerItem:TriggerInfoItem;
			for(var i:int=0; i<triggers.length; i++){
				triggerItem = triggers[i] as TriggerInfoItem;
				var func:Function;
				if(triggerItem.isSetter){
					addTriggerProperty(triggerItem.path, host, triggerItem.methodName, triggerItem.nsURI, triggerItem.commitOnly);
//					func = new SetterTrigger(host, triggerItem.methodName, triggerItem.nsURI, triggerItem.path).handler;
				}else{
					addTriggerFunction(triggerItem.path, host, triggerItem.methodName, triggerItem.nsURI, triggerItem.commitOnly);
//					func = new MethodTrigger(host, triggerItem.methodName, triggerItem.nsURI, triggerItem.path).handler;
				}
			}
		}
		
		
		/**
		 * Manually add a trigger, without metadata.
		 * This is handy, for example, if using a pre-compiler to remove [Trigger] metadata, which would remove the need for the trigger host type to be 
		 * introspected at runtime.
		 */
		public function addTriggerProperty(path:Array, host:Object, propertyName:String, propertyNsURI:String=null, commitOnly:Boolean=false):void
		{
			var func:Function = new SetterTrigger(host, propertyName, propertyNsURI, path).handler;
			ChangeWatcher.watch(host, path, func, commitOnly);
		}
		
		public function addTriggerFunction(path:Array, host:Object, methodName:String, methodNsURI:String=null, commitOnly:Boolean=false):void
		{	
			var func:Function = 
				new MethodTrigger(host, methodName, methodNsURI, path).handler;
			ChangeWatcher.watch(host, path, func, commitOnly);
		}
	}
}



import flash.events.Event;
import mx.logging.Log;
import memorphic.logging.LogType;	


class BaseTrigger
{
	public var host:Object;
	public var chain:Array;
	
	public function BaseTrigger(host:Object, chain:Array)
	{
		this.host = host;
		this.chain = chain;
	}
	
	protected function getValue():*
	{
		try{
			var n:int = chain.length;
			var value:Object = host;
			for(var i:int=0; i<n; i++){
				value = value[chain[i]];
			}
		}catch(e:Error){
			Log.getLogger(LogType.UI).warn("[Trigger] Error peforming binding to '" + chain.join(".") + "'.");
		}
		return value;
	}
}

class SetterTrigger extends BaseTrigger
{
	public var propName:*;
	
	public function SetterTrigger(host:Object, propName:String, nsURI:String, chain:Array)
	{
		super(host, chain);
		if(!nsURI){
			this.propName = propName;
		}else{
			this.propName = new QName(new Namespace(nsURI), propName);
		}
	}
	
	public function handler(e:Event):void
	{
		try {
			host[propName] = getValue();
		}catch(e:Error){
			Log.getLogger(LogType.UI).error("[Trigger] Error binding to setter. " + e.message);
		}
	}
}


class MethodTrigger  extends BaseTrigger
{
	public var methodName:*;
	
	public function MethodTrigger(host:Object, methodName:String, nsURI:String, chain:Array)
	{
		super(host, chain);
		
		if(!nsURI){
			this.methodName = methodName;
		}else{
			this.methodName = new QName(new Namespace(nsURI), methodName);
		}
	}
	

	public function handler(e:Event):void
	{
		try{
			host[methodName](getValue());
		}catch(e:Error){
			Log.getLogger(LogType.UI).error("[Trigger] Error binding to method. " + e.message);
		}catch(e:ArgumentError){
			try{
				host[methodName]();
			}catch(e2:Error){
				Log.getLogger(LogType.UI).error("[Trigger] Error binding to method. " + e.message);
			}
		}
	}
}

